using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.Runtime.InteropServices;

using MSHelpLib;
using MSHelpLib.BaseCode;
using MSHelpLib.CHM.Decoding;

namespace MSHelpLib.HXS
{
	internal class HXSSectionEntry 
	{
		public void ReadHeader(BinaryReader st)
		{
			SectionOffset=st.ReadUInt64();
			SectionLength=st.ReadUInt64();
		}

		public UInt64	SectionOffset;
		public UInt64	SectionLength;
	}

	internal class HXSPreDirectoryHeader
	{
		public UInt32 Version;
		public UInt32 CAOLOffset;	// from beginning of post-header)

		public void ReadHeader(BinaryReader st)
		{
			Version=st.ReadUInt32();
			CAOLOffset=st.ReadUInt32();			
		}
	}

	internal class HXSDirectoryInfoHeader
	{		
		public UInt64 TopLevelChunkNumber;	// of top-level AOLI chunk in directory, or -1
		public UInt64 FirstAOLLChunkNumber;
		public UInt64 LastChunkNumber;	// of last AOLL chunk in directory
		public UInt64 Res1;
		public UInt32 DirectoryChunkSize;
		public UInt32 Quickref; // density for main directory, usually 2
		public UInt32 Res2;
		public UInt32 DirDepth; //  of main directory index tree
		// 1 there is no index, 2 if there is one level of AOLI chunks.
		public UInt64	Res3;
		public UInt64	DirectoryEntriesCount;
		
		public void ReadHeader(BinaryReader st)
		{			
			TopLevelChunkNumber=st.ReadUInt64();
			FirstAOLLChunkNumber=st.ReadUInt64();
			LastChunkNumber=st.ReadUInt64();
			Res1=st.ReadUInt64();
			DirectoryChunkSize=st.ReadUInt32();
			Quickref=st.ReadUInt32();
			Res2=st.ReadUInt32();
			DirDepth=st.ReadUInt32();		
			Res3=st.ReadUInt64();
			DirectoryEntriesCount=st.ReadUInt64();			
		}
	}

	internal  class HXSDirectoryIndexInfoHeader
	{		
		public UInt64 TopLevelChunkNumber;	// of top-level AOLI chunk in directory, or -1
		public UInt64 FirstAOLLChunkNumber;
		public UInt64 LastChunkNumber;	// of last AOLL chunk in directory
		public UInt64 Res1;
		public UInt32 DirectoryChunkSize;
		public UInt32 Quickref; // density for main directory, usually 2
		public UInt32 Res2;
		public UInt32 DirDepth; //  of main directory index tree
		// 1 there is no index, 2 if there is one level of AOLI chunks.
		public UInt64	Res3;
		public UInt64	DirectoryEntriesCount;
		
		public void ReadHeader(BinaryReader st)
		{			
			TopLevelChunkNumber=st.ReadUInt64();
			FirstAOLLChunkNumber=st.ReadUInt64();
			LastChunkNumber=st.ReadUInt64();
			Res1=st.ReadUInt64();
			DirectoryChunkSize=st.ReadUInt32();
			Quickref=st.ReadUInt32();
			Res2=st.ReadUInt32();
			DirDepth=st.ReadUInt32();		
			Res3=st.ReadUInt64();
			DirectoryEntriesCount=st.ReadUInt64();			
		}
	}

	internal class HXSPostDirectoryHeader
	{			
		public UInt32	Res1;	// 0088: DWORD    $100000 (Same as field following chunk size in directory)
		public UInt32	Res2;	// 008C: DWORD    $20000 (Same as field following chunk size in directory index)
		
		public UInt64	Res3;
		public UInt32	idCAOL;
		public UInt32	Version;	
		public UInt32	CAOLSectionLength;  // 00A0: DWORD    $50 (Length of the CAOL section, which includes the ITSF section)
		public UInt16	Res4;				// Remains the same when identical files are built. Does not appear to be a checksum.  Many files have
		// 'HH' (HTML Help?) here, indicating this may be a compiler ID field.  But at least one ITOL/ITLS compiler does not set this
		//	field to a constant value.
		public UInt16	Res5;				//  (Unknown.  Possibly part of 00A4 field)
		public UInt32	Res6;				// DWORD    Unknown.  Two values have been seen -- $43ED, and 0.
		public UInt32	DirectoryChunkSize;	// 00AC: DWORD    $2000 (Directory chunk size of directory)
		public UInt32	DirectoryIndexChunkSize;
		public UInt32	DirectoryQuickRef;	//  DWORD    $100000 (Same as field following chunk size in directory)
		public UInt32	DirectoryIndexQuickRef;	// 00B8: DWORD    $20000 (Same as field following chunk size in directoryindex)
		public UInt32	Res7;
		public UInt32	Res8;
		public UInt32	Res9;
		public UInt32	idITSF;				//: ASCII    'ITSF' (ITStorage File?)
		public UInt32	CHMVersion;
		public UInt32	ITSFLength;
		public UInt32	Res10;
		public UInt64	ContentStream0Offset;
		public UInt32	TimeStamp;			// DWORD    A timestamp of some sort.  
		// Considered as a big-endian DWORD, it appears to contain
		// seconds (MSB) and fractional seconds (second byte).
		// The third and fourth bytes may contain even more fractional
		// bits.  The 4 least significant bits in the last byte are constant.
		public UInt32	LangCode;			// 00E4: DWORD    Windows language ID  (0x0409 = ENGLISH/ENGLISH-US)
		// (0x0407 = LANG_GERMAN/SUBLANG_GERMAN)

		public void ReadHeader(BinaryReader st)
		{	
			Res1=st.ReadUInt32();
			Res2=st.ReadUInt32();
			Res3=st.ReadUInt64();
			idCAOL=st.ReadUInt32();
			Version=st.ReadUInt32();	
			CAOLSectionLength=st.ReadUInt32();
			Res4=st.ReadUInt16();									
			Res5=st.ReadUInt16();
			Res6=st.ReadUInt32();
			DirectoryChunkSize=st.ReadUInt32();
			DirectoryIndexChunkSize=st.ReadUInt32();
			DirectoryQuickRef=st.ReadUInt32();
			DirectoryIndexQuickRef=st.ReadUInt32();
			Res7=st.ReadUInt32();
			Res8=st.ReadUInt32();
			Res9=st.ReadUInt32();
			idITSF=st.ReadUInt32();
			CHMVersion=st.ReadUInt32();
			ITSFLength=st.ReadUInt32();
			Res10=st.ReadUInt32();
			ContentStream0Offset=st.ReadUInt64();
			TimeStamp=st.ReadUInt32();
			LangCode=st.ReadUInt32();
		}
	}

	internal class HXSDirectoryListingHeader
	{
		public UInt32	idIFCM;
		public UInt32	Version;	// 0004: DWORD    1 (probably a version number)
		public UInt32	DirectoryChunkSize;
		public UInt32	Res1;
		public UInt32	Res2;
		public UInt32	Res3;
		public UInt32	ChunkCount;	// 0018: DWORD    Number of directory chunks
		public UInt32	Res4;		// 001C: DWORD    0 (unknown, probably high word of above)

		public void ReadHeader(BinaryReader st)
		{
			idIFCM=st.ReadUInt32();
			Version=st.ReadUInt32();
			DirectoryChunkSize=st.ReadUInt32();
			Res1=st.ReadUInt32();
			Res2=st.ReadUInt32();
			Res3=st.ReadUInt32();
			ChunkCount=st.ReadUInt32();
			Res4=st.ReadUInt32();
		}
	}

	internal class HXSDirectoryChunk
	{
		public UInt32	idAOLL;	
		public UInt32	QuickRefLength;	// 0004: DWORD    Length of quickref area at end of directory chunk
		public UInt64	ChunkNumber;	// This must match physical position in file, that is the chunk size times the chunk number must be the
										// offset from the end of the directory header.
		public UInt64	PrevChunkNumber;	// Chunk number of previous listing chunk when reading directory in sequence (-1 if first listing chunk)
		public UInt64	NextChunkNumber;	// of next listing chunk when reading directory in sequence (-1 if last listing chunk)
		public UInt64	FirstEntryNumberInChunk;	// Number of first listing entry in this chunk
		public UInt32	Res1;
		public UInt32	Res2;

        /// <summary>
        /// Reads the header.
        /// </summary>
        /// <param name="st">The st.</param>
		public void ReadHeader(BinaryReader st)
		{
			idAOLL=st.ReadUInt32();
			QuickRefLength=st.ReadUInt32();
			ChunkNumber=st.ReadUInt64();
			PrevChunkNumber=st.ReadUInt64();
			NextChunkNumber=st.ReadUInt64();
			FirstEntryNumberInChunk=st.ReadUInt64();
			Res1=st.ReadUInt32();
			Res2=st.ReadUInt32();
		}
	}

    /// <summary>
    /// 
    /// </summary>
	public class HXSDirectoryListingEntry : IFileInfo
	{

        /// <summary>
        /// 
        /// </summary>
		public IHelpStream m_HelpStream=null;
        /// <summary>
        /// 
        /// </summary>
		public UInt16 NameLength;
        /// <summary>
        /// 
        /// </summary>
		public string FileName;
        /// <summary>
        /// 
        /// </summary>
		public UInt16 ContentStreamNumber;
        /// <summary>
        /// 
        /// </summary>
		public UInt64 Offset;
        /// <summary>
        /// 
        /// </summary>
		public UInt64 Length;

		/// <summary>
		/// Internal helper for reading ENCINT encoded integer values
		/// </summary>
		/// <param name="st">reference to the reader</param>
		/// <returns>a long value</returns>
		private UInt64 ReadENCINT(BinaryReader st)
		{
			UInt64 accum = 0;
			byte temp=0;			
			while ((temp=st.ReadByte()) >= 0x80)
			{
				accum <<= 7;
				accum += (ulong)(temp & 0x7f);
			}

			return (accum << 7) + temp;
		}

        /// <summary>
        /// Reads the header.
        /// </summary>
        /// <param name="st">The st.</param>
		public void ReadHeader(BinaryReader st)
		{
			NameLength=(UInt16)ReadENCINT(st);			
			byte [] buf=st.ReadBytes(NameLength);
			FileName=Encoding.ASCII.GetString(buf,0,buf.Length);
			ContentStreamNumber=(UInt16)ReadENCINT(st);
			Offset=(UInt64)ReadENCINT(st);
			Length=(UInt64)ReadENCINT(st);
		}

		#region IFileInfo Members

        /// <summary>
        /// Gets the URL.
        /// </summary>
        /// <value>The URL.</value>
		public string URL
		{
			get
			{
				if (this.HelpStream==null) 
					return this.FileName;
				if (this.HelpStream.HelpFile==null)
					return this.FileName;

				string sURL=this.HelpStream.HelpFile.UrlPrefix+this.HelpStream.FileName+"::"+this.FileName;
				return sURL;
			}
		}

        /// <summary>
        /// Gets or sets the help stream.
        /// </summary>
        /// <value>The help stream.</value>
		public IHelpStream HelpStream 
		{ 
			get { return m_HelpStream;}
			set { m_HelpStream=value;}
		}

		string MSHelpLib.IFileInfo.FileName
		{
			get
			{
				return FileName;
			}
		}

		UInt64 MSHelpLib.IFileInfo.Offset
		{
			get
			{
				return Offset;
			}
		}

		UInt64 MSHelpLib.IFileInfo.Length
		{
			get
			{
				return Length;
			}
		}

        /// <summary>
        /// Gets the stream.
        /// </summary>
        /// <value>The stream.</value>
		public UInt16 Stream
		{
			get
			{
				return ContentStreamNumber;
			}
		}

		#endregion
	}

    /// <summary>
    /// 
    /// </summary>
	public class HXSDirectoryIndexChunk
	{
        /// <summary>
        /// 
        /// </summary>
		public UInt32 idAOLI;
        /// <summary>
        /// 
        /// </summary>
		public UInt32 QuickRefLength;
        /// <summary>
        /// 
        /// </summary>
		public UInt64	ChunkNumber;

        /// <summary>
        /// Reads the header.
        /// </summary>
        /// <param name="st">The st.</param>
		public void ReadHeader(BinaryReader st)
		{
			idAOLI=st.ReadUInt32();
			QuickRefLength=st.ReadUInt32();
			ChunkNumber=st.ReadUInt64();
		}
	}

    /// <summary>
    /// 
    /// </summary>
	public class HXSDirectoryIndexListingEntry
	{

        /// <summary>
        /// Reads the ENCINT.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <returns></returns>
		private UInt64 ReadENCINT(BinaryReader st)
		{
			UInt64 accum = 0;
			byte temp=0;			
			while ((temp=st.ReadByte()) >= 0x80)
			{
				accum <<= 7;
				accum += (ulong)(temp & 0x7f);
			}

			return (accum << 7) + temp;
		}

        /// <summary>
        /// 
        /// </summary>
		public UInt32 Length;
        /// <summary>
        /// 
        /// </summary>
		public string Name;
        /// <summary>
        /// 
        /// </summary>
		public UInt64 DirectoryListingChunk;	// ENCINT: directory listing chunk which starts with name		

        /// <summary>
        /// Reads the header.
        /// </summary>
        /// <param name="st">The st.</param>
		public void ReadHeader(BinaryReader st)
		{			
			Length=(UInt32)ReadENCINT(st);			
			byte [] buf=st.ReadBytes((int)Length);
			Name=Encoding.ASCII.GetString(buf,0,buf.Length);
			DirectoryListingChunk=(UInt64)ReadENCINT(st);
		}
	}

	internal class HXSContentHeader
	{
		public UInt16 FileLengthW;
		public UInt16 EntryCount;

		public ArrayList StreamNames=new ArrayList();

		public void ReadHeader(BinaryReader st)
		{
			FileLengthW=st.ReadUInt16();
			EntryCount=st.ReadUInt16();

			for(int i=0;i<EntryCount;i++)
			{
				UInt16 Len=st.ReadUInt16();				
				byte [] buf=st.ReadBytes((int)Len*2);
				string Name=Encoding.Unicode.GetString(buf,0,buf.Length);

				// Read Null Char
				st.ReadUInt16();
				StreamNames.Add(Name);
			}
		}	
	}

	internal class HXSStreamData 
	{
		public string	StreamName;
		public long		Offset;
		public bool		Compressed;
	}

	internal class HXSLzxcControlData:BaseStructure
	{
		public const int _CHM_LZXC_MIN_LEN=0x18;
		public const int _CHM_LZXC_V2_LEN=0x1c; 

		public UInt32   size=0;                   //  0        
		public char[]	signature=null;			  //  4 (LZXC) 
		public UInt32   version=0;                //  8        
		public UInt32   resetInterval=0;          //  c        
		public UInt32   windowSize=0;             // 10        
		public UInt32   windowsPerReset=0;        // 14        
		public UInt32   unknown_18=0;             // 18        
	
		public int Read_lzxc_control_data(BinaryReader st)
		{
			size=st.ReadUInt32()*4;
			signature=st.ReadChars(4);

			if (CheckSig("LZXC",signature)==false)
				return 0;

			version=st.ReadUInt32();
			resetInterval=st.ReadUInt32();
			windowSize=st.ReadUInt32();
			windowsPerReset=st.ReadUInt32();			

			if (size>=_CHM_LZXC_V2_LEN)
				unknown_18=st.ReadUInt32();
			else
				unknown_18 = 0;

			if ((version == 2) || (version == 3))
			{
				resetInterval *= 0x8000;
				windowSize *= 0x8000;				
			}
			if (windowSize == 0  ||  resetInterval == 0)
				return 0;

			// for now, only support resetInterval a multiple of windowSize/2 
			if (windowSize == 1)
				return 0;
			if ((resetInterval % (windowSize/2)) != 0)
				return 0;
	
			return 1;
		}
	}

    /// <summary>
    /// 
    /// </summary>
	public class HXSStream : IHelpStream
	{
        /// <summary>
        /// 
        /// </summary>
		public string	HeaderID;		// char[8]  'ITOLITLS' (hence the name, ITOL/ITLS)
        /// <summary>
        /// 
        /// </summary>
		public UInt32	Version;		// 1 (probably a version number)
        /// <summary>
        /// 
        /// </summary>
		public UInt32	AddressofHeaderSectionTable; // =$28
        /// <summary>
        /// 
        /// </summary>
		public UInt32	NumberOfEntriesInSectionTable;	// = 5
        /// <summary>
        /// 
        /// </summary>
		public UInt32	LengthOfPostHeaderTable;		//
        /// <summary>
        /// 
        /// </summary>
		public Guid		GUID;
        /// <summary>
        /// 
        /// </summary>
		public ArrayList SectionTable=null;

		private long StartOffset=0;
        /// <summary>
        /// 
        /// </summary>
		public	long m_DirectoryOffset=0;

        /// <summary>
        /// 
        /// </summary>
		public ArrayList			StreamTable=new ArrayList();
		private chmLzxcResetTable	reset_table=null;
		private	lzw					lzx_state=null;
		private int                 lzx_last_block=0;

		// LZX control data 
		private int					window_size=0;
		private UInt32              reset_interval=0;
		private UInt32              reset_blkcount=0;
		private UInt64              data_offset=0;
		private BinaryReaderEx		st=null;

		private ArrayList			IndexTable=new ArrayList();
		private ArrayList			DirTable=new ArrayList();

		private HXSDirectoryListingEntry	rt_unit=null;
		private HXSDirectoryListingEntry	cn_unit=null;
		private HXSDirectoryListingHeader	directoryListingHeader=null;
		private	long ContentStreamOffset=0;
		private IHelpFile			m_HelpFile=null;

		// public ArrayList FileDir=new ArrayList();

        /// <summary>
        /// Gets or sets the help file.
        /// </summary>
        /// <value>The help file.</value>
		public IHelpFile HelpFile
		{
			get { return m_HelpFile; }
			set { m_HelpFile=value; }
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="HXSStream"/> class.
        /// </summary>
		public HXSStream()
		{
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="HXSStream"/> class.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <param name="_helpFile">The _help file.</param>
		public HXSStream(string FileName, IHelpFile _helpFile)
		{			
			Open(FileName,_helpFile);
		}

        /// <summary>
        /// Closes this instance.
        /// </summary>
		public void Close()
		{
			st.Close();
			st=null;
		}

        /// <summary>
        /// Opens the specified file name.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <param name="_helpFile">The _help file.</param>
        /// <returns></returns>
		public bool Open(string FileName, IHelpFile _helpFile)
		{
			m_HelpFile=_helpFile;
			if (!File.Exists(FileName))
				return false;

			m_FileName=FileName;
			FileStream stFile=new FileStream(FileName,FileMode.Open,FileAccess.Read,FileShare.Read);
			st=new BinaryReaderEx(stFile);

			PEFile peFile=new PEFile(st);
			if (!peFile.IsMZFile())
				return false;

			peFile.MoveToStartOfSection(".its");
			
			return ReadHeader(st);
		}

		private int FileCount=0;
        /// <summary>
        /// Gets the number of files.
        /// </summary>
        /// <value>The number of files.</value>
		public int NumberOfFiles
		{
			get { return FileCount; }
		}

        /// <summary>
        /// Reads the header.
        /// </summary>
        /// <param name="InputStream">The input stream.</param>
        /// <returns></returns>
		public bool ReadHeader(BinaryReaderEx InputStream)
		{
			st=InputStream;
			StartOffset=st.BaseStream.Position;

			byte [] SecName=st.ReadBytes(8);
			int j=0;
			for(j=0;j<SecName.Length;j++)
			{
				if (SecName[j]==0)
					break;
			}
			HeaderID=Encoding.ASCII.GetString(SecName,0,j);
			if (HeaderID.ToLower()!="itolitls")
				return false;

			Version=st.ReadUInt32();
			AddressofHeaderSectionTable=st.ReadUInt32();
			NumberOfEntriesInSectionTable=st.ReadUInt32();
			LengthOfPostHeaderTable=st.ReadUInt32();
			GUID=new Guid(st.ReadBytes(16));
			
			// Read Section Table
			SectionTable=new ArrayList();
			for(int i=0;i<NumberOfEntriesInSectionTable;i++)
			{
				HXSSectionEntry SectionEntry =new HXSSectionEntry ();
				SectionEntry.ReadHeader(st);
				SectionTable.Add(SectionEntry);
			}

			// Read Pre Directory Info Header
			HXSPreDirectoryHeader preDirectoryHeader=new HXSPreDirectoryHeader();
			preDirectoryHeader.ReadHeader(st);

			// Read Directory Information
			HXSDirectoryInfoHeader dirInfoHeader=new HXSDirectoryInfoHeader();
			dirInfoHeader.ReadHeader(st);

			// Read Directory Index Information
			HXSDirectoryIndexInfoHeader dirInfoIndexHeader=new HXSDirectoryIndexInfoHeader();
			dirInfoIndexHeader.ReadHeader(st);

			// Read Post Directory Info Header
			HXSPostDirectoryHeader postDirectoryHeader=new HXSPostDirectoryHeader();
			postDirectoryHeader.ReadHeader(st);

			// Find Start of File Directory Data - Move to Start of Section 1
			HXSSectionEntry DirSectionEntry=(HXSSectionEntry)SectionTable[1];
			m_DirectoryOffset=StartOffset+(long)DirSectionEntry.SectionOffset;
			st.BaseStream.Seek(m_DirectoryOffset,SeekOrigin.Begin);		

			directoryListingHeader=new HXSDirectoryListingHeader();
			directoryListingHeader.ReadHeader(st);
			
			ArrayList HXSFiles=new ArrayList();

			// Read Directory chunks
			for(int Chunk=0;Chunk<directoryListingHeader.ChunkCount;Chunk++)
			{					
				long Offset=m_DirectoryOffset+(directoryListingHeader.DirectoryChunkSize*Chunk)+0x20; // 0x40 =  sizeof HXSDirectoryListingHeader IFCM Header				
				st.BaseStream.Seek(Offset,SeekOrigin.Begin);
                // Trace.WriteLine("Directory Chunk = 0x" + Chunk.ToString("x04"));

				// Peek to see if we have an Index or Directory chunk following.
				UInt32 Header=st.ReadUInt32();
				st.BaseStream.Seek(-4,SeekOrigin.Current);
				if (Header==0x494c4f41)
				{
					// Index Chunk				
					HXSDirectoryIndexChunk directoryIndexChunk=new HXSDirectoryIndexChunk();
					directoryIndexChunk.ReadHeader(st);					
					long end=(long)st.BaseStream.Position+(long)directoryListingHeader.DirectoryChunkSize - directoryIndexChunk.QuickRefLength - 0x20 - 0x10 ; // 0x20 = size of IFCM Header - HXSDirectoryListingHeader					
					do
					{
						HXSDirectoryIndexListingEntry entry=new HXSDirectoryIndexListingEntry();
						entry.ReadHeader(st);
						IndexTable.Add(entry);
                        // Trace.Indent();
						// Trace.WriteLine("Index Entry Name : "+entry.Name);
                        // Trace.Unindent();
					}
					while (st.BaseStream.Position<end);					
				}
				else if (Header==0x4c4c4f41)
				{
					// File Directory Chunk
					HXSDirectoryChunk directoryChunk=new HXSDirectoryChunk();
					directoryChunk.ReadHeader(st);

					long end=(long)st.BaseStream.Position+(long)directoryListingHeader.DirectoryChunkSize - directoryChunk.QuickRefLength - 0x20 - 0x10 ; // 0x20 = size of IFCM Header - HXSDirectoryListingHeader
							
					// Trace.Indent();
					do
					{
						HXSDirectoryListingEntry entry=new HXSDirectoryListingEntry();
						entry.ReadHeader(st);
						entry.m_HelpStream=this;
						FileCount++;
						
						if (entry.FileName.StartsWith("::DataSpace/"))
							HXSFiles.Add(entry);

                        // Trace.WriteLine(entry.FileName);
					}
					while (st.BaseStream.Position<end);	
					// Trace.Unindent();
				}
				else
				{
					throw(new ApplicationException("Unknown header code - Failed to read file correctly, or file corrupt"));
				}
			}

			// Find Start of streams
			ContentStreamOffset=(long)(this.StartOffset+(long)postDirectoryHeader.ContentStream0Offset);
			st.BaseStream.Seek(ContentStreamOffset,SeekOrigin.Begin);

			bool bCompressionEnabled=false;
			HXSStreamData CompressedStream=null;
			HXSContentHeader contentHeader=null;
			UInt64 StreamLen=0;

			foreach(HXSDirectoryListingEntry entry in HXSFiles)
			{
				if (entry.FileName=="::DataSpace/NameList")
				{
					st.BaseStream.Seek(ContentStreamOffset+(long)entry.Offset,SeekOrigin.Begin);
					contentHeader=new HXSContentHeader();
					contentHeader.ReadHeader(st);

					foreach(string StreamName in contentHeader.StreamNames)
					{
						HXSStreamData StreamData=new HXSStreamData();
						StreamData.StreamName=StreamName;
						StreamData.Offset=ContentStreamOffset;
						StreamData.Compressed=false;
						if (StreamName=="MSCompressed")
						{
							StreamData.Compressed=true;
							bCompressionEnabled=true;
							CompressedStream=StreamData;
						}
						StreamTable.Add(StreamData);
					}
				}
			}			

			HXSLzxcControlData ctlData=null;
			if (bCompressionEnabled)
			{
				string Transform="";
				foreach(HXSDirectoryListingEntry entry in HXSFiles)
				{
					if (entry.FileName=="::DataSpace/Storage/"+CompressedStream.StreamName+"/Content")
					{
						CompressedStream.Offset=(long)entry.Offset+ContentStreamOffset;
						data_offset=(ulong)CompressedStream.Offset;
					}

					if (entry.FileName=="::DataSpace/Storage/"+CompressedStream.StreamName+"/ControlData")
					{
						st.BaseStream.Seek((long)entry.Offset+ContentStreamOffset,SeekOrigin.Begin);
						ctlData=new HXSLzxcControlData();	
						ctlData.Read_lzxc_control_data(st);
						cn_unit=entry;

						window_size = (int)ctlData.windowSize;
						reset_interval = ctlData.resetInterval;
						try
						{
							reset_blkcount = (uint)(reset_interval / (window_size / 2) * ctlData.windowsPerReset);
						}
						catch(Exception)
						{
							reset_blkcount=0;
						}

					}
					if (entry.FileName=="::DataSpace/Storage/"+CompressedStream.StreamName+"/Transform/List")
					{
						st.BaseStream.Seek((long)entry.Offset+ContentStreamOffset,SeekOrigin.Begin);
						Guid TransformGUID=new Guid(st.ReadBytes(16));
						Transform=TransformGUID.ToString("B").ToUpper();
					}

					if (entry.FileName=="::DataSpace/Storage/"+CompressedStream.StreamName+"/SpanInfo")
					{
						st.BaseStream.Seek((long)entry.Offset+ContentStreamOffset,SeekOrigin.Begin);
						StreamLen=st.ReadUInt64();
					}
				}

				string BaseName="::DataSpace/Storage/"+CompressedStream.StreamName+"/Transform/"+Transform+"/";
				foreach(HXSDirectoryListingEntry entry in HXSFiles)
				{					
					if (entry.FileName==BaseName+"InstanceData/ResetTable")
					{
						st.BaseStream.Seek((long)entry.Offset+ContentStreamOffset,SeekOrigin.Begin);
						reset_table=new chmLzxcResetTable();
						reset_table.Read_lzxc_reset_table(st);
						rt_unit=entry;
					}
				}
			}
			return true;
		}				

		private string m_FileName="";
        /// <summary>
        /// Gets the name of the file.
        /// </summary>
        /// <value>The name of the file.</value>
		public string FileName
		{
			get { return m_FileName;}
		}

		private string NormallizeFileName(string FileName)
		{
			string NewName=FileName.ToLower();
			NewName=NewName.Replace(@"\","/");
			if (!NewName.StartsWith("/"))
				NewName="/"+NewName;
			return NewName;
		}

        /// <summary>
        /// Finds the file.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public IFileInfo FindFile(string FileName)
		{		
			string m_FileFind=NormallizeFileName(FileName);

			// Find Directory Chunk where our file			
			UInt32 Chunk=0;
			int FileNameLen=FileName.Length;
			if (IndexTable.Count>1)
			{				
				for(int i=0; i<IndexTable.Count;i++)
				{
					HXSDirectoryIndexListingEntry entry=(HXSDirectoryIndexListingEntry)IndexTable[i];
					string ExtractFileName=NormallizeFileName(entry.Name);
					int y=ExtractFileName.CompareTo(m_FileFind);
					if (y>=0)
					{
						if (i>0)
						{
							HXSDirectoryIndexListingEntry entry2=null;
							if (y==0)
								entry2=(HXSDirectoryIndexListingEntry)IndexTable[i];
							else
								entry2=(HXSDirectoryIndexListingEntry)IndexTable[i-1];
							
							Chunk=(UInt32)entry2.DirectoryListingChunk;
						}
						else
							Chunk=0;
						break;						
					}
				}				
			}

			if (directoryListingHeader==null)
			{
				directoryListingHeader=new HXSDirectoryListingHeader();
				directoryListingHeader.ChunkCount=1;
				directoryListingHeader.DirectoryChunkSize=0x1000;
			}
			for(UInt32 i=Chunk;i<(int)directoryListingHeader.ChunkCount;i++)
			{
				long Offset=(long)(m_DirectoryOffset+(long)(directoryListingHeader.DirectoryChunkSize*i)+0x20); // 0x40 =  sizeof HXSDirectoryListingHeader IFCM Header
				st.BaseStream.Seek(Offset,SeekOrigin.Begin);		

				// Peek to see if we have an Index or Directory chunk following.
				UInt32 Header=st.ReadUInt32();
				st.BaseStream.Seek(-4,SeekOrigin.Current);
				if (Header==0x494c4f41)
				{
					continue;
				}
				else if (Header!=0x4c4c4f41)
				{
					throw(new ApplicationException("Error - Index Pointing to Index Chunk 2"));
				}
				else
				{
					// File Directory Chunk
					HXSDirectoryChunk directoryChunk=new HXSDirectoryChunk();
					directoryChunk.ReadHeader(st);

					long end=(long)st.BaseStream.Position+(long)directoryListingHeader.DirectoryChunkSize - directoryChunk.QuickRefLength - 0x20 - 0x10 ; // 0x20 = size of IFCM Header - HXSDirectoryListingHeader				
										
					do
					{
						HXSDirectoryListingEntry entry=new HXSDirectoryListingEntry();
						entry.ReadHeader(st);
						entry.HelpStream=this;

						string LocalFile=this.NormallizeFileName(entry.FileName);
						if (m_FileFind.Length<=LocalFile.Length)
						{				
							if (LocalFile.IndexOf(m_FileFind)==LocalFile.Length-m_FileFind.Length)
							{
								return entry;
							}			
						}
					}
					while (st.BaseStream.Position<end);	                
				}
			}
            Debug.WriteLine("Failed to find file : ", m_FileFind);
			return null;
		}

        /// <summary>
        /// Extracts the specified file name.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public MemoryStream Extract(string FileName)
		{
			IFileInfo fileInfo=FindFile(FileName);
			return Extract(fileInfo);
		}

        /// <summary>
        /// Extracts the specified entry.
        /// </summary>
        /// <param name="entry">The entry.</param>
        /// <returns></returns>
		public MemoryStream Extract(IFileInfo entry)
		{		
			if (entry==null)
				return null;

			if (entry.Length==0)
				return null;			
			
			HXSStreamData CurrentStream=(HXSStreamData)StreamTable[entry.Stream];
			
			MemoryStream stFile=new MemoryStream((int)(entry.Length+1000));
			switch(entry.Stream)
			{
				case 0:
				{
					// Extract File
					st.BaseStream.Seek((long)((long)CurrentStream.Offset+(long)entry.Offset),SeekOrigin.Begin);					
					byte [] buf=st.ReadBytes((int)entry.Length);
					stFile.Write(buf,0,buf.GetLength(0));					
				}
					break;

				case 1 :
				{												
					ulong len=entry.Length;
					ulong addr=0;
					ulong swath=0, total=0;						
					do 
					{
						// swill another mouthful 
						swath = _chm_decompress_region(stFile, entry.Offset+addr, len);

						// if we didn't get any... 
						if (swath == 0)
						{
							Trace.Assert((total!=len),"De-compress failed","Length Required = "+len.ToString()+" Length returned = "+total.ToString());
							break;
						}

						// update stats 
						total += swath;
						len -= swath;
						addr += swath;
					} while (len != 0);		
					lzx_state=null;					
				}
				break;

				default:
				{						
					throw(new ApplicationException("Undefined stream"));
				}
			}
			stFile.Flush();
			stFile.Seek(0,SeekOrigin.Begin);
			return stFile;
		}

        /// <summary>
        /// Opens the stream.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public MemoryStream OpenStream(string FileName)
		{
			try
			{
				IFileInfo entry=FindFile(FileName);
				if (entry!=null)
					return this.Extract(entry);
				else
					return null;
			}
			catch
			{
				return null;
			}
		}

        /// <summary>
        /// Processes all files.
        /// </summary>
        /// <param name="processAllFiles">The process all files.</param>
        /// <returns></returns>
		public int ProcessAllFiles(MSHelpLib.ProcessAllFilesDelegate processAllFiles)
		{				
			for(int Chunk=0;Chunk<this.directoryListingHeader.ChunkCount;Chunk++)
			{					
				long Offset=m_DirectoryOffset+(directoryListingHeader.DirectoryChunkSize*Chunk)+0x20; // 0x40 =  sizeof HXSDirectoryListingHeader IFCM Header				
				st.BaseStream.Seek(Offset,SeekOrigin.Begin);		

				// Peek to see if we have an Index or Directory chunk following.
				UInt32 Header=st.ReadUInt32();
				st.BaseStream.Seek(-4,SeekOrigin.Current);
				if (Header==0x494c4f41)
				{
					// Skip Index Chunks
				}
				else if (Header==0x4c4c4f41)
				{
					// File Directory Chunk
					HXSDirectoryChunk directoryChunk=new HXSDirectoryChunk();
					directoryChunk.ReadHeader(st);

					long end=(long)st.BaseStream.Position+(long)directoryListingHeader.DirectoryChunkSize - directoryChunk.QuickRefLength - 0x20 - 0x10 ; // 0x20 = size of IFCM Header - HXSDirectoryListingHeader
					do
					{
						HXSDirectoryListingEntry entry=new HXSDirectoryListingEntry();
						entry.ReadHeader(st);
						entry.HelpStream=this;

						// Save current file pos
						long FilePos=st.BaseStream.Position;

						// Process file
						PAFEnum rc=processAllFiles(entry);

						// restore file position
						st.BaseStream.Seek(FilePos,SeekOrigin.Begin);

						if (rc==PAFEnum.Abort)
							break;
					}
					while (st.BaseStream.Position<end);						
				}
				else
				{
					throw(new ApplicationException("Unknown header code - Failed to read file correctly, or file corrupt"));
				}
			}
			return 1;
		}	

		#region FindFileByExt
		private string FindFIleByExt="";
		private MSHelpLib.IFileInfo InfoFileByExt=null;
		private PAFEnum FindFileByExtension(IFileInfo fileInfo)
		{
			if (Path.GetExtension(fileInfo.FileName).ToLower()==FindFIleByExt)
			{
				InfoFileByExt=fileInfo;
				return PAFEnum.Abort;
			}
			else
				return PAFEnum.Continue;
		}

        /// <summary>
        /// Finds the file by ext.
        /// </summary>
        /// <param name="Ext">The ext.</param>
        /// <returns></returns>
		public MSHelpLib.IFileInfo FindFileByExt(string Ext)
		{
			if (!Ext.StartsWith("."))
				return null;

			FindFIleByExt=Ext.ToLower();
			InfoFileByExt=null;
			ProcessAllFiles(new MSHelpLib.ProcessAllFilesDelegate(FindFileByExtension));
			return InfoFileByExt;
		}
		#endregion

		#region Decompression wrapper functions

		// grab a region from a compressed block 
		private ulong _chm_decompress_region(Stream buf, ulong  start, ulong len)
		{
			ulong nBlock, nOffset;
			ulong nLen;
			ulong gotLen;

			if (len <= 0)
				return (Int64)0;

			// figure out what we need to read 
			nBlock = start / reset_table.block_len;
			nOffset = start % reset_table.block_len;
			nLen = len;
			if (nLen > (reset_table.block_len - nOffset))
				nLen = reset_table.block_len - nOffset;

			// data request not satisfied, so... start up the decompressor machine 			
			if (lzx_state==null)
			{
				int window_size = ffs(this.window_size) - 1;
				lzx_last_block = -1;

				lzx_state=new lzw();
				lzx_state.LZXinit(window_size);
			}

			// decompress some data 
			MemoryStream ms=new MemoryStream((int)reset_table.block_len+6144);			
			gotLen = _chm_decompress_block(nBlock, ms);
			if (gotLen < nLen)
				nLen = gotLen;	
			
			// memcpy(buf, ubuffer+nOffset, (unsigned int)nLen);
			ms.Position=(long)nOffset;
			for(ulong i=0;i<nLen;i++)
				buf.WriteByte((byte)ms.ReadByte());
			buf.Flush();
			return nLen;
		}

		// decompress the block.  must have lzx_mutex. 
		private ulong _chm_decompress_block(UInt64 block, System.IO.Stream OutBuffer)
		{
			// byte []cbuffer = new byte(reset_table.block_len + 6144);
			ulong cmpStart=0;                                    // compressed start  
			ulong cmpLen=0;                                       // compressed len    
			UInt32 blockAlign = (UInt32)(block % reset_blkcount); // reset intvl. aln. 			
			
			// check if we need previous blocks 
			if (blockAlign != 0)			
			{
				/* fetch all required previous blocks since last reset */
				for (UInt32 i = blockAlign; i > 0; i--)
				{
					UInt32 curBlockIdx = (UInt32)(block-i);
					
					/* check if we most recently decompressed the previous block */
					if ((ulong)lzx_last_block != curBlockIdx)
					{
						if ((curBlockIdx % reset_blkcount)==0)
						{
							lzx_state.LZXreset();
						}
						
						_chm_get_cmpblock_bounds(st,curBlockIdx, ref cmpStart, ref cmpLen);
						st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin);						
						if (lzx_state.LZXdecompress(st,OutBuffer, ref cmpLen, ref reset_table.block_len) != lzw.DECR_OK)
							return (Int64)0;
					}
					lzx_last_block = (int)(curBlockIdx);
				}
			}
			else
			{
				if ((block % reset_blkcount)==0)
				{
					lzx_state.LZXreset();
				}		
			}

			// decompress the block we actually want 
			if (_chm_get_cmpblock_bounds(st, block, ref cmpStart, ref cmpLen)==0)
				return 0;

			st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin);
							
			if (lzx_state.LZXdecompress(st, OutBuffer, ref cmpLen,ref reset_table.block_len) != lzw.DECR_OK)
				return (Int64)0;

			lzx_last_block = (int)block;

			// XXX: modify LZX routines to return the length of the data they
			// * decompressed and return that instead, for an extra sanity check.
			return reset_table.block_len;
		}

		//	* utility methods for dealing with compressed data		 
		// get the bounds of a compressed block.  return 0 on failure 			
		private int _chm_get_cmpblock_bounds(System.IO.BinaryReader st, UInt64 block, ref UInt64 start, ref UInt64 len)
		{
			// for all but the last block, use the reset table 
			if (block < reset_table.block_count-1)
			{
				// unpack the start address				
				st.BaseStream.Seek((long)ContentStreamOffset + (long)rt_unit.Offset+ (long)reset_table.table_offset + (long)(block*8),SeekOrigin.Begin);
				start=st.ReadUInt64();
				len=st.ReadUInt64();
			}

				// for the last block, use the span in addition to the reset table 
			else
			{				
				// unpack the start address 
				st.BaseStream.Seek((long)ContentStreamOffset + (long)rt_unit.Offset + (long)reset_table.table_offset + (long)(block*8),SeekOrigin.Begin);
				start=st.ReadUInt64();
				len = reset_table.compressed_len;
			}

			// compute the length and absolute start address 
			len -= start;
			start += data_offset; //  + cn_unit.Offset;

			return 1;
		}

		private int ffs(int val)
		{
			int bit=1;
			int idx=1;
			while (bit != 0  &&  (val & bit) == 0)
			{
				bit <<= 1;
				++idx;
			}
			if (bit == 0)
				return 0;
			else
				return idx;
		}


		#endregion			
	}
}
